﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using agsXMPP;
using agsXMPP.protocol.client;
using agsXMPP.protocol.iq.roster;
using agsXMPP.Xml.Dom;
using log4net;
using Jabr.GUI.Forms;

namespace Jabr.Logic.Roster
{
    [XmlRoot(ElementName = "jabber-acc")]
    public class JabberAccount : AbstractAccount
    {
        private XmppClientConnection m_connection;
        private Jid m_jid;
        private Presence m_pres;
        private List<DisplayableItem> m_roster;

        public override event MessageDelegate OnMessageReceived;
        public override event LoginDelegate OnLogin;
        public override event LoginDelegate OnRegister;
        public override event RosterDelegate OnRoster;

        public JabberAccount()
            : base()
        {
            this.m_connection = new XmppClientConnection();
            this.m_pres = new Presence();
            this.m_connection.AutoAgents = true;
            this.m_connection.AutoRoster = true;
            this.m_connection.Resource = "Jabr";
            this.m_connection.ClientVersion = "Jabr " + Assembly.GetEntryAssembly().GetName().Version.ToString();
            this.m_connection.Priority = 5;
            base.m_host = this.m_connection.Server;
            base.m_port = this.m_connection.Port;
            base.m_user = this.m_connection.Username;
            base.m_password = this.m_connection.Password;

            this.m_connection.OnError += delegate(object sender, Exception ex)
            {
                #if DEBUG
                    throw ex;
                #endif
            };
            this.m_connection.OnMessage += delegate(object sender, Message msg)
            {
                GenericMessage gm = new GenericMessage();
                gm.Body = msg.Body;
                gm.From = msg.From.Bare;
                gm.Subject = msg.Subject;
                switch (msg.Type)
                {
                    case MessageType.chat:
                        gm.Type = GenericMessageType.CHAT;
                        break;
                    case MessageType.normal:
                        gm.Type = GenericMessageType.MESSAGE;
                        break;
                    default:
                        gm.Type = GenericMessageType.OTHER;
                        break;
                }
                if (this.OnMessageReceived != null)
                    this.OnMessageReceived(gm);
            };
            this.m_connection.OnReadXml += delegate(object sender, string xml)
            {
                ILog logger = LogManager.GetLogger("jabr-logger");
                if (logger.IsDebugEnabled)
                    logger.Debug("RECEIVE (" + this.m_jid.ToString() + "): " + xml);
            };
            this.m_connection.OnWriteXml += delegate(object sender, string xml)
            {
                ILog logger = LogManager.GetLogger("jabr-logger");
                if (logger.IsDebugEnabled)
                    logger.Debug("SEND (" + this.m_jid.ToString() + "): " + xml);
            };
            this.m_connection.OnRegistered += delegate(object sender)
            {
                if (this.OnRegister != null)
                    this.OnRegister(LoginResultType.SUCCESS, null);
            };
            this.m_connection.OnRegisterError += delegate(object sender, agsXMPP.Xml.Dom.Element e)
            {
                if (this.OnRegister != null)
                    this.OnRegister(LoginResultType.FAILED, e);
            };
            this.m_connection.OnAuthError += delegate(object sender, agsXMPP.Xml.Dom.Element e)
            {
                if (this.OnLogin != null)
                    this.OnLogin(LoginResultType.FAILED, e);
            };
            this.m_connection.OnLogin += delegate(object sender)
            {
                this.m_connection.Send(this.m_pres);
                if (this.OnLogin != null)
                    this.OnLogin(LoginResultType.SUCCESS, null);
            };
            this.m_connection.OnRosterStart += delegate(object sender)
            {
                this.m_roster = new List<DisplayableItem>();
            };
            this.m_connection.OnRosterItem += delegate(object sender, RosterItem item)
            {
                var dc = new DisplayableContact(item.Jid.Bare, item.Name);
                dc.Account = this;
                this.m_connection.OnPresence += delegate(object sender_in, Presence pres)
                {
                    if (dc.ContactID == pres.From.Bare)
                    {
                        if (pres.Show == ShowType.NONE && pres.Type == PresenceType.available)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.ONLINE;
                        }
                        else if (pres.Show == ShowType.away && pres.Type == PresenceType.available)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.AWAY;
                        }
                        else if (pres.Show == ShowType.xa && pres.Type == PresenceType.available)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.XA;
                        }
                        else if (pres.Show == ShowType.dnd && pres.Type == PresenceType.available)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.DND;
                        }
                        else if (pres.Show == ShowType.chat && pres.Type == PresenceType.available)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.FREEFORCHAT;
                        }
                        else if (pres.Type == PresenceType.invisible)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.INVISIBLE;
                        }
                        else if (pres.Type == PresenceType.unavailable)
                        {
                            dc.PresenceStatus = pres.Status;
                            dc.PresenceValue = GenericPresence.OFFLINE;
                        }
                    }
                };
                foreach (Element g in item.GetGroups())
                {
                    IEnumerable<DisplayableItem> res = from r in this.m_roster where r.GetType() == typeof(DisplayableGroup) && r.Name == g.Value select r;
                    if (res.Count() == 0)
                    {
                        var dg = new DisplayableGroup(g.Value);
                        dc.GroupAdd(dg);
                        this.m_roster.Add(dg);
                    }
                    else
                    {
                        if(Jabr.GUI.Forms.RosterForm.Instance.InvokeRequired == false) // Patchwork
                            foreach (DisplayableGroup dg in res)
                                dc.GroupAdd(dg);
                    }
                }
                if (item.GetGroups().Count == 0)
                    this.m_roster.Add(dc);
                dc.IsInitialized = true;
            };
            this.m_connection.OnRosterEnd += delegate(object sender)
            {
                if (this.OnRoster != null)
                    this.OnRoster(this.m_roster);
            };
            this.m_connection.OnMessage += delegate(object sender, Message msg)
            {
                if (msg.Type == MessageType.normal)
                {
                    var gm = new GenericMessage(GenericMessageType.MESSAGE);
                    gm.Body = msg.Body;
                    gm.Subject = msg.Subject;
                    gm.From = msg.From.Bare;
                    var mf = new MessageForm(gm);
                    RosterForm.Instance.Invoke(new RosterForm.InvokeFormDelegate(RosterForm.Instance.InvokeForm), mf);
                }
                else if (msg.Type == MessageType.chat)
                {
                    RosterForm.Instance.ChatFromTo(msg.From.Bare, msg.To.Bare, msg.Body);
                }
            };
        }

        ~JabberAccount()
        {
            this.Disconnect();
        }

        public override object Clone()
        {
            JabberAccount ja = new JabberAccount();
            ja.Username = this.Username;
            ja.UserID = this.UserID;
            ja.Password = this.Password;
            ja.Host = this.Host;
            ja.Port = this.Port;
            ja.Resource = this.Resource;
            ja.Priority = this.Priority;
            ja.Display = null;
            return ja;
        }
        public override int CompareTo(AbstractAccount other)
        {
            JabberAccount ja = (JabberAccount)other;
            int res = 0;
            res = this.UserID.CompareTo(ja.UserID);
            if (res != 0)
                return res;
            res = this.Password.CompareTo(ja.Password);
            if (res != 0)
                return res;
            res = this.Host.CompareTo(ja.Host);
            if (res != 0)
                return res;
            res = this.Port.CompareTo(ja.Port);
            if (res != 0)
                return res;
            res = this.Resource.CompareTo(ja.Resource);
            if (res != 0)
                return res;
            res = this.Priority.CompareTo(ja.Priority);
            if (res != 0)
                return res;
            return res;
        }

        public override string Type
        {
            get
            {
                return "Jabber XMPP";
            }
        }
        public override string Form
        {
            get
            {
                return "Jabr.GUI.Forms.AccountJabberEdit";
            }
        }
        public override string Host
        {
            get
            {
                return this.m_connection.Server;
            }
            set
            {
                base.Host = value;
                this.m_connection.Server = value;
            }
        }
        public override int Port
        {
            get
            {
                return base.Port;
            }
            set
            {
                if (base.Port > 0)
                {
                    base.Port = value;
                    this.m_connection.Port = value;
                }
            }
        }
        public override string UserID
        {
            get
            {
                return base.UserID;
            }
            set
            {
                m_jid = new Jid(value);
                this.m_connection.Username = m_jid.User;
                this.Host = m_jid.Server;
                base.UserID = m_jid.Bare;
            }
        }
        public override string Password
        {
            get
            {
                return base.Password;
            }
            set
            {
                base.Password = value;
                this.m_connection.Password = value;
            }
        }
        [XmlElement(ElementName = "Resource")]
        public string Resource
        {
            get
            {
                return this.m_connection.Resource;
            }
            set
            {
                this.m_connection.Resource = value;
            }
        }
        [XmlElement(ElementName = "Priority")]
        public int Priority
        {
            get
            {
                return this.m_connection.Priority;
            }
            set
            {
                this.m_connection.Priority = value;
            }
        }
        public override bool New
        {
            get
            {
                return base.New;
            }
            set
            {
                base.New = this.m_connection.RegisterAccount = value;
            }
        }

        public override void ContactRemove(string id)
        {
            this.ContactUnsubscribe(id);
            this.m_connection.RosterManager.RemoveRosterItem(new Jid(id));
        }
        public override void ContactSubscribe(string id)
        {
            this.m_connection.PresenceManager.Subcribe(new Jid(id));
        }
        public override void ContactUnsubscribe(string id)
        {
            Presence p = new Presence();
            p.To = new Jid(id);
            p.Type = PresenceType.unsubscribed;
            this.m_connection.Send(p);
        }
        public override void ContactAdd(string id, string name, string group)
        {
            if (string.IsNullOrEmpty(group))
                this.m_connection.RosterManager.AddRosterItem(new Jid(id), name);
            else
                this.m_connection.RosterManager.AddRosterItem(new Jid(id), name, group);
            this.ContactSubscribe(id);
        }
        public override void ContactUpdate(string id, string name, string group)
        {
            if (string.IsNullOrEmpty(group))
                this.m_connection.RosterManager.UpdateRosterItem(new Jid(id), name);
            else
                this.m_connection.RosterManager.UpdateRosterItem(new Jid(id), name, group);
        }

        public override void ChatTo(string id, string chat)
        {
            Message m = new Message(id, chat);
            m.Type = MessageType.chat;
            this.m_connection.Send(m);
        }

        public override void PresenceTo(string id, GenericPresence presence, string status)
        {
            throw new System.NotImplementedException();
        }
        public override void PresenceFrom(string id)
        {
            Presence p = new Presence();
            p.To = new Jid(id);
            p.From = this.m_jid;
            p.Type = PresenceType.probe;
            this.m_connection.Send(p);
        }

        [XmlIgnore]
        public override GenericPresence Presence
        {
            get
            {
                return base.Presence;
            }
            set
            {
                base.Presence = value;
                this.m_pres = new Presence();
                switch (value)
                {
                    default:
                    case GenericPresence.OFFLINE:
                        this.m_pres.Type = PresenceType.unavailable;
                        this.m_pres.Show = ShowType.NONE;
                        break;
                    case GenericPresence.INVISIBLE:
                        this.m_pres.Type = PresenceType.invisible;
                        this.m_pres.Show = ShowType.NONE;
                        break;
                    case GenericPresence.FREEFORCHAT:
                        this.m_pres.Type = PresenceType.available;
                        this.m_pres.Show = ShowType.chat;
                        break;
                    case GenericPresence.ONLINE:
                        this.m_pres.Type = PresenceType.available;
                        this.m_pres.Show = ShowType.NONE;
                        break;
                    case GenericPresence.AWAY:
                        this.m_pres.Type = PresenceType.available;
                        this.m_pres.Show = ShowType.away;
                        break;
                    case GenericPresence.XA:
                        this.m_pres.Type = PresenceType.available;
                        this.m_pres.Show = ShowType.xa;
                        break;
                    case GenericPresence.DND:
                        this.m_pres.Type = PresenceType.available;
                        this.m_pres.Show = ShowType.dnd;
                        break;
                }
                this.m_pres.Priority = this.Priority;
                this.m_pres.Status = this.Status;
                if (!this.m_connection.Authenticated)
                {
                    this.Connect();
                    return;
                }
                else
                {
                    this.m_connection.Send(this.m_pres);
                }
            }
        }
        public override string Status
        {
            get
            {
                return base.Status;
            }
            set
            {
                base.Status = value;
            }
        }

        public override object Connection
        {
            get
            {
                return this.m_connection;
            }
        }

        public override void Connect()
        {
            this.m_connection.Open();
        }

        public override void Disconnect()
        {
            this.m_connection.Close();
        }
    }
}
